package com.chaos.meta.config;

import com.chaos.meta.entity.User;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Comparator;
import java.util.List;

/**
 * @author naicha
 * @dateTime: 2020:08:24 14:12:50
 * @description: custom session manage
 */
public class CustomControlAuthenticationStrategy implements SessionAuthenticationStrategy {
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private SessionRegistry sessionRegistry;
    private int maximumSessions = -1;

    /**
     * @param sessionRegistry the session registry which should be updated when the
     * authenticated session is changed.
     */
    public CustomControlAuthenticationStrategy(SessionRegistry sessionRegistry, int maximumSessions) {
        Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null");
        this.maximumSessions = maximumSessions;
        this.sessionRegistry = sessionRegistry;
    }

    /**
     * In addition to the steps from the superclass, the sessionRegistry will be updated
     * with the new session information.
     */
    @SuppressWarnings(value={"unchecked"})
    public void onAuthentication(Authentication authentication,
                                 HttpServletRequest request, HttpServletResponse response) {

        final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
                authentication.getPrincipal(), false);

        boolean isExecutorUser = false;
        boolean sessionIsValid = false;

        int sessionCount = sessions.size();
        int allowedSessions = this.maximumSessions;

        User user = (User) authentication.getPrincipal();
        List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) user.getAuthorities();
        if(authorities.size() == 1 && "ROLE_Executor".equalsIgnoreCase(authorities.get(0).toString()))
        {
            isExecutorUser = true;
        }

        // Executor User can create many session
        if(isExecutorUser)
        {
            sessionIsValid = true;
        }else if (sessionCount < allowedSessions) {
            // They haven't got too many login sessions running at present
            sessionIsValid = true;
        }else if(allowedSessions == -1) {
            // permit unlimited logins
            sessionIsValid = true;
        }else if(sessionCount == allowedSessions)
        {
            HttpSession session = request.getSession(false);
            if (session != null) {
                //clear user's sessions and create a new one
                for (SessionInformation si : sessions) {
                    if (!si.getSessionId().equals(session.getId())) {
                        si.expireNow();
//                        sessionRegistry.removeSessionInformation(si.getSessionId());
                    }
                    sessionIsValid = false;
                }
            }else {
                sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
            }
        }

        if (sessionIsValid)
        {
            // if success, then register the session
            sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
        }else {
            allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
        }
    }

    /**
     * Allows subclasses to customise behaviour when too many sessions are detected.
     *
     * @param sessions either <code>null</code> or all unexpired sessions associated with
     * the principal
     * @param allowableSessions the number of concurrent sessions the user is allowed to
     * have
     * @param registry an instance of the <code>SessionRegistry</code> for subclass use
     *
     */
    protected void allowableSessionsExceeded(List<SessionInformation> sessions,
                                             int allowableSessions, SessionRegistry registry)
            throws SessionAuthenticationException {
        if (sessions == null) {
            throw new SessionAuthenticationException(messages.getMessage(
                    "ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
                    new Object[] {allowableSessions},
                    "Maximum sessions of {0} for this principal exceeded"));
        }

        // Determine least recently used sessions, and mark them for invalidation
        sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
        int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
        List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
        for (SessionInformation session: sessionsToBeExpired) {
            session.expireNow();
        }
    }
}